package cn.cj.erp.common.exception;

import cn.cj.erp.common.constants.BizCodeEnum;
import cn.cj.erp.common.exception.ServiceException;
import cn.cj.erp.common.http.Result;
import cn.cj.erp.common.security.security.user.LoginUser;
import cn.cj.erp.common.security.security.utils.SecurityUtils;
import cn.cj.erp.common.utils.LogUtils;
import cn.cj.erp.common.utils.ServletUtils;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Optional;

/**
 * controller 接口异常处理器
 *
 * @author chenjian
 */
@Slf4j
@RestControllerAdvice
public class ServiceExceptionHandler {

    /**
     * 处理自定义异常
     * @param e 自定义异常
     * @return Result
     */
    @ExceptionHandler(ServiceException.class)
    public Result<String> handleServiceException(ServiceException e) {
        log.error(logPrefix(), e);
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 数据库插入重复异常
     * @param e 数据库插入重复异常
     * @return Result
     */
    @ExceptionHandler(DuplicateKeyException.class)
    public Result<String> handleDuplicateKeyException(DuplicateKeyException e) {
        log.error(logPrefix(), e);
        return Result.error("数据库中已存在该记录");
    }

    /**
     * 加了@RequestParam注解，但是接口调用时没有传指定的参数
     * <p>注意：是没有传，而不是传了，但是值是null</p>。
     * @param e 参数校验异常
     * @return Result
     */
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    public Result<String> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        String errorMessage = String.format("%s[%s]", BizCodeEnum.NO_PARAM.getMsg(), e.getParameterName());
        log.error(logPrefix(), e);
        return Result.error(BizCodeEnum.NO_PARAM.getCode(), errorMessage);
    }

    /**
     * 校验的参数在类中，接口是post请求，用{@code @Valid @RequestBody}方式接受参数时，如果报错，会被该捕获器捕获。
     * @param e 参数校验异常
     * @return Result
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    public Result<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        String errorMessage = null == error ? BizCodeEnum.ERR_PARAM.getMsg() : error.getDefaultMessage();
        log.error(logPrefix(), e);
        return Result.error(BizCodeEnum.ERR_PARAM.getCode(), errorMessage);
    }

    /**
     * 校验参数在类中，接口是get请求，报错会被该捕获器捕获。
     * @param e 参数校验异常
     * @return Result
     */
    @ExceptionHandler(value = {BindException.class})
    public Result<String> handleBindException(BindException e) {
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        String errorMessage = null == error ? BizCodeEnum.ERR_PARAM.getMsg() : error.getDefaultMessage();
        log.error(logPrefix(), e);
        return Result.error(BizCodeEnum.ERR_PARAM.getCode(), errorMessage);
    }

    /**
     * 经过测试，当校验参数直接写在接口上，而不是写在类中，报错会被该捕获器捕获。
     *
     * @param e 参数校验异常
     * @return Result
     */
    @ExceptionHandler(value = {ConstraintViolationException.class})
    public Result<String> handleConstraintViolationException(ConstraintViolationException e) {
        Optional<ConstraintViolation<?>> first = e.getConstraintViolations().stream().findFirst();
        String errorMessage = first.isPresent() ? first.get().getMessage() : BizCodeEnum.ERR_PARAM.getMsg();
        log.error(logPrefix(), e);
        return Result.error(BizCodeEnum.ERR_PARAM.getCode(), errorMessage);
    }

    /**
     * 兜底异常
     * @param e 其他异常
     * @return Result
     */
    @ExceptionHandler(Exception.class)
    public Result<String> handleException(Exception e) {
        log.error(logPrefix(), e);
        return Result.error();
    }

    /**
     * 获取日志头
     * @return 日志头
     */
    private String logPrefix() {
        String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return LogUtils.getBlock(ip) +
                LogUtils.getBlock(loginUser.getUsername()) +
                LogUtils.getBlock(ServletUtils.getRequest().getRequestURI()) +
                " exception: ";
    }

}
